home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Personal Computer World 2009 February
/
PCWFEB09.iso
/
Software
/
Linux
/
Kubuntu 8.10
/
kubuntu-8.10-desktop-i386.iso
/
casper
/
filesystem.squashfs
/
usr
/
sbin
/
update-python-modules
< prev
next >
Wrap
Text File
|
2008-06-30
|
14KB
|
385 lines
#! /usr/bin/python
#
# copyright (c) 2006 Josselin Mouette <joss@debian.org>
# Licensed under the GNU Lesser General Public License, version 2.1
# See COPYING for details
import sys,os,shutil
from optparse import OptionParser
from py_compile import compile, PyCompileError
sys.path.append("/usr/lib/python-support/private/")
import pysupport
from pysupport import py_supported,py_installed,py_oldversions
basepath='/var/lib/python-support'
sourcepath='/usr/share/python-support'
extensionpath='/usr/lib/python-support'
parser = OptionParser(usage="usage: %prog [-v] [-c] package_directory [...]\n"+
" %prog [-v] [-c] package.dirs [...]\n"+
" %prog [-v] [-a|-f|-p]")
parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
help="verbose output", default=False)
parser.add_option("-c", "--clean", action="store_true", dest="clean_mode",
help="clean modules instead of compiling them",
default=False)
parser.add_option("-a", "--rebuild-all", action="store_true",
dest="rebuild_all", default=False,
help="rebuild all private modules for a new default python version")
parser.add_option("-f", "--force-rebuild-all", action="store_true",
dest="rebuild_everything", default=False,
help="rebuild all modules, including public modules for all python versions")
parser.add_option("-p", "--post-install", action="store_true", dest="post_install",
help="run post-installation operations, common to many packages",
default=False)
parser.add_option("-b", "--bytecompile", action="store_true", dest="force_private",
help="[deprecated] byte-compilation mode: only handle private modules",
default=False)
parser.add_option("-i", "--install", action="store_true", dest="force_public",
help="[deprecated] installation mode: only handle public modules",
default=False)
(options, args) = parser.parse_args()
def debug(x):
if(options.verbose):
print x
# I should use the sets type instead
def isect(l1,l2):
return [i for i in l1 if i in l2]
def concat(l1,l2):
return l1 + [i for i in l2 if i not in l1]
versions_dict={}
def dir_versions(dir):
if dir not in versions_dict:
verfile=os.path.join(dir,'.version')
if dir.startswith(extensionpath):
# Directory in /usr/lib: only one version
vers=os.path.split(dir)[1]
if vers in py_supported:
versions_dict[dir]=[vers]
else:
versions_dict[dir]=[]
elif dir.startswith(sourcepath):
# Directory in /usr/share
extdir=dir.replace(sourcepath,extensionpath,1)
if os.path.exists(verfile):
# If we have a .version, use it
versions_dict[dir]=pysupport.version_list(file(verfile).readline())
elif os.path.isdir(extdir):
# Try to obtain the list of supported versions
# from the extensions in /usr/lib
versions_dict[dir]=isect(py_supported,os.listdir(extdir))
else:
# Otherwise, support all versions
versions_dict[dir]=py_supported
else:
raise "[Internal error] %s: unsupported path for byte-compilation."
return versions_dict[dir]
def bytecompile_only(basedir,dir,file):
if file.endswith('.py'):
fullpath=os.path.join(basedir,dir,file)
debug("compile "+fullpath+'c')
try:
# Note that compile doesn't raise PyCompileError by default
compile(fullpath, doraise=True)
except IOError, (errno, strerror):
sys.stderr.write("WARNING: I/O error while trying to byte-compile %s (%s): %s\n" % (fullpath, errno, strerror))
except PyCompileError, inst:
sys.stderr.write("WARNING: compile error while trying to byte-compile %s: %s\n" % (fullpath, inst.msg))
except:
sys.stderr.write("WARNING: unexpected error while trying to byte-compile %s: %s\n" % (fullpath, sys.exc_info()[0]))
def clean_simple(basedir,dir,file):
if file.endswith('.py'):
for ext in ['c','o']:
fullpath=os.path.join(basedir,dir,file+ext)
if os.path.exists(fullpath):
debug("remove "+fullpath)
os.remove(fullpath)
def install_modules(versions):
def install_modules_func(basedir,dir,file):
if file == '.version':
return
fullpath=os.path.join(basedir,dir,file)
for py in isect(dir_versions(basedir),versions):
destpath=os.path.join(basepath,py,dir,file)
try:
os.makedirs(os.path.join(basepath,py,dir))
except OSError:
pass
if file[-4:] not in ['.pyc','.pyo']:
debug("link "+destpath)
# os.path.exists returns False for broken symbolic links
if os.path.exists(destpath) or os.path.islink(destpath):
if file!="__init__.py" or (os.path.exists(destpath) and os.path.getsize(destpath)):
# Oops, the file already exists and is not empty.
# Check whether we are conflicting with something else.
for otherdir in dirs_i:
otherextdir = os.path.join(otherdir.replace(sourcepath,extensionpath,1),py)
if basedir in [otherdir,otherextdir]:
continue
if os.path.exists(os.path.join(otherdir,dir,file)) or os.path.exists(os.path.join(otherextdir,dir,file)):
parser.error("Trying to overwrite %s which is already provided by %s"%(os.path.join(dir,file),otherdir))
# The file is already here, probably from the previous version.
# Let's proceed.
debug("overwrite "+destpath)
else:
debug("overwrite namespace "+destpath)
os.remove(destpath)
os.symlink(fullpath,destpath)
# Files are NOT byte-compiled here, this will be done later.
return install_modules_func
def process(basedir,func):
debug("Looking at %s..."%(basedir))
for dir, dirs, files in os.walk(basedir):
dir = dir[len(basedir):].lstrip('/')
for file in files:
func(basedir, dir, file)
for file in dirs:
if os.path.islink(os.path.join(basedir,dir,file)):
func(basedir, dir, file)
def process_extensions(basedir,func,version=None):
basedir=basedir.replace(sourcepath,extensionpath,1)
if os.path.isdir(basedir):
for vers in os.listdir(basedir):
if version and vers != version:
continue
verdir=os.path.join(basedir,vers)
if os.path.isdir(verdir):
process(verdir,func([vers]))
def dirlist_file(f):
return [ l.rstrip('\n') for l in file(f) if len(l)>1 ]
def bytecompile_all(py,path=None):
if not path:
path=os.path.join(basepath,py)
if not os.path.isdir(path):
return
debug("Byte-compilation of whole %s..."%path)
os.spawnl(os.P_WAIT, '/usr/bin/'+py, py,
os.path.join('/usr/lib/',py,'compileall.py'), '-q', path)
def bytecompile_privatedir(basedir):
versionfile=os.path.join(basedir,".pyversion")
if os.path.isfile(versionfile):
specific_version=file(versionfile).readline().rstrip('\n')
bytecompile_all("python"+specific_version,basedir)
else:
process(basedir,bytecompile_only)
def create_dotpath(py,dirhash=None):
path=os.path.join(basepath,py)
pathfile=os.path.join(path,".path")
debug("Generation of %s..."%pathfile)
pathlist=[path]
for f in os.listdir(path):
f=os.path.join(path,f)
if f.endswith(".pth") and os.path.isfile(f):
for l in file(f):
l=l.rstrip('\n')
pathlist.append(l)
l2=os.path.join(path,l)
pathlist.append(l2)
if dirhash:
dirhash[l2]=False
fd=file(pathfile,"w")
fd.writelines([l+'\n' for l in pathlist])
fd.close()
def post_change_stuff(py):
# All the changes that need to be done after anything has changed
# in a /var/lib/python-support/pythonX.Y directory
# * Cleanup of all dangling symlinks that are left out after a package
# is upgraded/removed.
# * The namespace packages are here because python doesn't consider a
# directory to be able to contain packages if there is no __init__.py
# file (yes, this is completely stupid).
# * The .path file must be created by concatenating all those .pth
# files that extend sys.path (this also badly sucks).
# * Byte-compilation of all .py files that haven't already been
path=os.path.join(basepath,py)
if not os.path.isdir(path):
return
# First, remove any dangling symlinks.
# In the same loop, we find which directories may need a namespace package
dirhash={}
for dir, dirs, files in os.walk(path):
dirhash[dir]=False
files.sort() # We need the .py to appear before the .pyc
for f in files+dirs:
# We also examine dirs as some symlinks are dirs
abspath=os.path.join(dir,f)
islink=os.path.islink(abspath)
if islink:
if not os.path.exists(abspath):
# We refer to a file that was removed
debug("remove "+abspath)
os.remove(abspath)
continue
srcfile = os.readlink (abspath)
# Remove links left here after a change in the supported python versions for a package
if srcfile.startswith(sourcepath):
if py not in dir_versions (os.path.join(sourcepath,srcfile[len(sourcepath)+1:].split("/",1)[0])):
debug("remove "+abspath)
os.remove(abspath)
continue
if f[-4:] in ['.pyc', '.pyo']:
if not os.path.exists(abspath[:-1]):
debug("remove "+abspath)
os.remove(abspath)
continue
elif f[-3:] in ['.py', '.so']:
if islink or f!='__init__.py':
# List the directory as maybe needing a namespace packages
d=dir
while dirhash.has_key(d) and not dirhash[d]:
dirhash[d]=True
d=os.path.dirname(d)
# Remove the directory if it is empty after our crazy removals
try:
os.removedirs(dir)
except OSError:
pass
dirhash[path]=False
# Then, find which directories belong in a .pth file
# These directories don't need a namespace package, so we
# pass the dirhash
create_dotpath(py,dirhash)
# Finally, create/remove namespace packages
for dir in dirhash:
initfile=os.path.join(dir,"__init__.py")
if dirhash[dir]:
if not os.path.exists(initfile):
debug("create namespace "+initfile)
file(initfile,"w").close()
else:
for e in ['','c','o']:
if os.path.exists(initfile+e):
debug('remove namespace '+initfile+e)
os.remove(initfile+e)
try:
os.removedirs(dir)
except OSError:
pass
bytecompile_all(py)
# Parse arguments
do_dirs_i=[]
do_dirs_b=[]
for arg in args:
if os.path.isabs(arg):
if not arg.startswith(sourcepath):
parser.error("%s is not in the python-support directory."%arg)
else:
arg=os.path.join(sourcepath,arg)
if not os.path.exists(arg):
if options.clean_mode:
sys.stderr.write("WARNING: %s does not exist.\n Some bytecompiled files may be left behind.\n"%arg)
continue
else:
parser.error("%s does not exist"%arg)
if arg.endswith('.dirs'):
do_dirs_b+=dirlist_file(arg)
if options.force_public:
parser.error("Option -i cannot be used with a private module .dirs file.")
elif os.path.isdir(arg):
do_dirs_i.append(arg)
if options.force_private:
parser.error("Option -b cannot be used with a public module directory.")
else:
parser.error("%s is not a directory"%arg)
# Read full list from the source directory
# directories are stuff to be installed
# foo.dirs files list directories to bytecompile in place
dirs_b = []
dirs_i = []
for f in os.listdir(sourcepath):
f=os.path.join(sourcepath,f)
if os.path.isdir(f):
dirs_i.append(f)
elif f.endswith('.dirs'):
dirs_b+=dirlist_file(f)
if not os.path.isdir(basepath):
os.mkdir(basepath)
if options.rebuild_everything:
options.rebuild_all = True
for pyver in py_supported:
dir = os.path.join(basepath,pyver)
if os.path.isdir(dir):
shutil.rmtree(dir)
# Check for changes in installed python versions
for pyver in py_oldversions+py_supported:
dir = os.path.join(basepath,pyver)
# Check for ".path" because sometimes the directory already exists
# while the python version isn't installed, because of some .so's.
if pyver in py_installed and not os.path.isfile(os.path.join(dir,".path")):
debug("Building all modules in %s..."%(dir))
for basedir in dirs_i:
process(basedir,install_modules([pyver]))
process_extensions(basedir,install_modules,pyver)
# Here we need to launch post_change_stuff because otherwise we could
# end up without the .path file that is checked 6 lines earlier
post_change_stuff (pyver)
if pyver not in py_installed and os.path.isdir(dir):
debug("Removing obsolete directory %s..."%(dir))
shutil.rmtree(dir)
if options.rebuild_all:
for basedir in dirs_b:
process(basedir,clean_simple)
bytecompile_privatedir(basedir)
# Now for the processing of what was handed on the command line
for basedir in do_dirs_b:
if not options.clean_mode:
bytecompile_privatedir(basedir)
else:
process(basedir,clean_simple)
need_dotpath = False
need_postinstall = []
for basedir in do_dirs_i:
need_postinstall = concat(need_postinstall,isect(dir_versions(basedir),py_installed))
if not options.clean_mode:
process(basedir,install_modules(py_installed))
process_extensions(basedir,install_modules)
for f in os.listdir(basedir):
if f.endswith(".pth"):
need_dotpath = True
# Only do the funny and time-consuming things when the -p option is
# given, e.g when python-support is triggered.
if need_postinstall and 'DPKG_RUNNING_VERSION' in os.environ and not options.post_install:
ret = os.spawnlp(os.P_WAIT, 'dpkg-trigger', 'dpkg-trigger', '--no-await', 'pysupport')
if ret:
sys.stderr.write("ERROR: dpkg-trigger failed\n")
sys.exit(1)
need_postinstall = []
if options.post_install:
# Now the trigger is activated, do it for all installed versions
need_postinstall = py_installed
if need_postinstall:
need_dotpath = False
for py in need_postinstall:
post_change_stuff(py)
if need_dotpath:
for py in need_postinstall:
create_dotpath (py)